---
title: Application security and authorization
summary: Authorization architecture, AD resource catalog, and enforcement classification for the 4 Active Directory resources reachable from ClaimBillEntry.
status: draft
owner: Claims
last_reviewed: 2026-10-01
---

# Application security and authorization

This page documents the application-wide authorization architecture for ClaimBillEntry, consolidating the authorization patterns reachable from the application's own controllers and the consumed `SAIF.SAIFCode.Core` package (`SAIF.Services` assembly). Authentication is handled entirely by infrastructure and is not application-level — all requests arrive with a `WindowsIdentity` established by IIS Windows authentication (NTLM or Kerberos negotiation), with anonymous access disabled. See [Tech debt](tech-debt.md#td-001-ntlm-authentication-dependency) for the NTLM finding.

## Authentication architecture

Authentication is handled entirely by infrastructure and is not part of application code. IIS establishes a `WindowsIdentity` on the request context before any ClaimBillEntry action runs; the application has no code for the authentication mechanism itself.

## Authorization architecture

ClaimBillEntry's authorization model layers an attribute-based controller role gate with a narrower, proven claim-level check, plus package-computed flags whose full consumption is not established by this run's evidence. The diagram below separates what is proven at each layer from what remains computed-but-unconsumed or untraced, per `ClaimBillEntry/Controllers/EntryController.cs:224-239` and `ClaimBillEntry/Controllers/BaseController.cs:31-34,38-48`.

```mermaid
flowchart TD
    Pkg["SAIF.Services ClaimSecurityService<br>computes HasConflictAccess and<br>HasSecureAccess (SVC-BR-SEC-01)"]
    Pkg --> ConflictFlag["HasConflictAccess"]
    Pkg --> SecureFlag["HasSecureAccess<br>(computed; no consuming<br>check found in traced code)"]
    User[User request] --> IIS[IIS Windows authentication]
    IIS --> Identity[WindowsIdentity established]
    Identity --> Scope{"Entry, Inbox, or<br>Search controller?"}
    Scope -->|"No"| Unguarded["No SaifAuthorize role check<br>(grid/provider controllers - TD-003)"]
    Scope -->|"Yes"| Attr["SaifAuthorizeAttribute<br>Roles = CBPEnterBill"]
    Attr -->|"Role missing"| Deny1[Access denied]
    Attr -->|"Role present"| GetBill["EntryController.GetBill"]
    GetBill --> ConflictCheck["UserHasAccessToClaim:<br>independent local check -<br>IsConflictClaim and<br>IsAuthorized(ConflictClaim)"]
    ConflictCheck -->|"Fails"| Deny2["Error: not authorized<br>to view claim"]
    ConflictCheck -->|"Passes"| Allow["Bill data returned"]
    BaseCheck["BaseController.CheckSecurity<br>override returns HasConflictAccess only"]
    ConflictFlag -.-> BaseCheck
    BaseCheck -.-> Unknown["Inherited ClaimControllerBase<br>CheckSecurity / action-filter mechanics:<br>unknown, not traced beyond call site"]
    Discard["ApplicationUser.IsAuthorized<br>(CBEDiscardBill)"] --> UIOnly["CanDiscardBill view-model flag:<br>UI button visibility only -<br>DiscardBill enforces no permission<br>beyond the CBPEnterBill role"]
```

`SaifAuthorizeAttribute`'s declared-role check, `IUserProfileService`/`ILogService`/`IEnvironmentService` requirements, and its `SecurityException`-and-redirect denial path are documented via the Legends CLI† in [Request pipeline and composition](operations/request-pipeline-and-composition.md). The diagram is scoped to the `Entry`, `Inbox`, and `Search` controllers, where `[SaifAuthorize(Roles = "CBPEnterBill")]` is applied; the grid controllers (`DiagnosisGrid`, `ProcedureGrid`, `ServiceGrid`, `NoteGrid`) and `ProviderController` carry no controller-level role attribute, per [Tech debt TD-003](tech-debt.md#td-003-authorization-enforced-on-the-client-or-missing) and [Hosted endpoints](hosted-endpoints.md). `EntryController.GetBill` (lines 83-95, 224-239) proves only its own, independent conflict-claim check: the local `UserHasAccessToClaim` method calls `IClaimInformationService.GetClaimSecurity(claimId).IsConflictClaim` and, when true, `ApplicationUser.IsAuthorized("ConflictClaim")` — this does not read or consume the package-computed `HasConflictAccess` flag. `BaseController.CheckSecurity` (overriding `ClaimControllerBase`) is the sole proven consumer of `HasConflictAccess`, returning `access.HasConflictAccess` only (`BaseController.cs:31-34`); no code path in `EntryController` or `BaseController` was found consuming `HasSecureAccess`/`SecureClaim`, even though `ClaimSecurityService` computes it† ([SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas)) — this is reported as a gap in proof, not as evidence the check does not exist elsewhere. `OnActionExecuting` skips the inherited base-class action-filter logic entirely for AJAX requests (`BaseController.cs:38-48`), so whether `CheckSecurity` actually runs on every request reaching `BaseController`-derived controllers is itself not fully traced. Internal mechanics of `SaifAuthorizeAttribute` (`SAIF.Framework.Web.Mvc`) beyond its call site are documented via retrieved behavior† above; `ClaimControllerBase.CheckSecurity` (`SAIF.SAIFCode.Web.Mvc`) internals beyond its call site remain untraced — unknown, not verified by this run**. `CBEDiscardBill` is checked only to populate the `CanDiscardBill` view-model flag that hides or shows the Discard button (`EntryController.cs:65,159-167,209`); `DiscardBill` itself performs no server-side authorization check beyond the controller-level `CBPEnterBill` role — see [BR-SCR-006](business-rules.md#br-scr-006-discard-permission-is-enforced-client-side-only).

## AD resource catalog

4 Active Directory resources are reachable from ClaimBillEntry's consumed interface surface.

| Resource name | Purpose | Libraries using | Enforcement type |
| --- | --- | --- | --- |
| `CBPEnterBill` | Primary controller-level gate — a user must hold this role to reach any action on the `Entry`, `Inbox`, or `Search` controllers | `SAIF.Framework.Web.Mvc` (`SaifAuthorizeAttribute`) | Attribute |
| `ConflictClaim` | Claim-level conflict access; `ClaimSecurityService` computes `HasConflictAccess`† ([SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas)); proven consumed only by `BaseController.CheckSecurity`. `EntryController.GetBill` separately performs its own independent local `ConflictClaim` check (`UserHasAccessToClaim`) that does not read this flag. | `SAIF.Services` (`ClaimSecurityService`) | Programmatic |
| `SecureClaim` | Claim-level secure access; `ClaimSecurityService` computes `HasSecureAccess`† ([SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas)), but no consuming check was found in traced `EntryController`/`BaseController` code — unknown whether any path enforces it** | `SAIF.Services` (`ClaimSecurityService`) | Programmatic (computed; consumption unproven) |
| `CBEDiscardBill` | Controls only the client-visible `CanDiscardBill` view-model flag; not a server-side discard gate | ClaimBillEntry (`EntryController`, local check only) | Programmatic (UI-visibility only) |

<!-- Scope exception: see "Scope exception" section below for why this catalog has 4 entries rather than the template's minimum of 5. -->

### Scope exception

The template's AD-resource catalog expects a minimum of 5 entries. ClaimBillEntry reaches only 4. This is a legitimate scope exception, not a gap: `SAIF.Services` declares roughly 75 AD resources in total, spanning Legal, Agency, Policy, Certificate, and other domain resources, but ClaimBillEntry's consumed interface surface — `IBillEntryService`, `IBillDiagnosisService`, `IBillProcedureService`, `IBillServiceService`, `INoteService`, `IProviderQueryProvider`, `IClaimInformationService`, `IClaimSecurityService`, plus the local `CBEDiscardBill` check and the `SAIF.Framework.Web.Mvc` `CBPEnterBill` attribute — never constructs or calls `PolicySecurityService`, `CertificateSecurityService`, or any legal/agency resource check. The remaining roughly 71 resources are correctly excluded as not reachable from any ClaimBillEntry entry point, not fabricated as ClaimBillEntry authorization and not padded into this catalog to reach a numeric minimum. ClaimBillEntry's application surface is narrow by design — three screens over a focused set of bill-entry services — so a 4-of-5+ catalog accurately reflects the reachable authorization resources for this application; full end-to-end consumption of every resource (notably `SecureClaim`) was not proven by this run's evidence.

## Security enforcement summary

| Layer | Enforcement mechanism | Count | Source |
| --- | --- | --- | --- |
| Presentation | `[SaifAuthorize(Roles = "CBPEnterBill")]` on `Entry`, `Inbox`, and `Search` controllers; no role attribute on the grid or `Provider` controllers | 1 resource | `SAIF.Framework.Web.Mvc` (`SaifAuthorizeAttribute`) |
| Business logic (claim-level, proven) | Programmatic `ClaimSecurityService.HasConflictAccess` via `IsAuthorized(ConflictClaim)`† ([SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas)), consumed only by `BaseController.CheckSecurity` | 1 resource | `SAIF.Services` (`ClaimSecurityService`), reached via `ClaimControllerBase`/`BaseController` |
| Business logic (claim-level, local, independent) | `EntryController.GetBill`'s local `UserHasAccessToClaim` check: `IClaimInformationService.GetClaimSecurity().IsConflictClaim` + `ApplicationUser.IsAuthorized("ConflictClaim")` — a separate enforcement point that does not read the `HasConflictAccess` flag above | 1 resource (same `ConflictClaim` AD resource, reached by an independent code path) | `ClaimBillEntry/Controllers/EntryController.cs:83-95,224-239` |
| Business logic (claim-level, computed but unconsumed) | `ClaimSecurityService.HasSecureAccess` via `IsAuthorized(SecureClaim)` is computed† ([SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas)), but no consuming check was found in traced `EntryController`/`BaseController` code — unknown whether it is enforced elsewhere** | 1 resource | `SAIF.Services` (`ClaimSecurityService`) |
| Business logic (bill-level, local, UI-only) | `ISystemUserProfile.IsAuthorized("CBEDiscardBill")` gates only the `CanDiscardBill` view-model flag; not a server-side gate | 1 resource | `ClaimBillEntry/Controllers/EntryController.cs` |
| Data access | No data-ownership validation beyond the claim-level checks above was traced | 0 | — |

## Cross-cutting authorization rules

### Bill entry requires the CBPEnterBill role

| Field | Value |
| --- | --- |
| **Statement** | A user must hold the `CBPEnterBill` role to reach any action on the `Entry`, `Inbox`, or `Search` controllers. |
| **Trigger** | Any request to a bill-entry, inbox, or search action. |
| **Enforcement** | Attribute-based: `[SaifAuthorize(Roles = "CBPEnterBill")]`, evaluated before the action method runs. |
| **Enforcing libraries** | `SAIF.Framework.Web.Mvc` (`SaifAuthorizeAttribute`) |
| **Outcome** | Request denied with a `SecurityException`-driven redirect when the role is missing; action proceeds when present. |
| **Related resources** | `CBPEnterBill` |

The conflict/secure claim access formulas and the bill discard permission check are not restated here as new rule identifiers, to avoid duplicating sanctioned IDs. The full `ClaimSecurityService` formula is defined once, as [SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas) in [Business rules](business-rules.md) — see the "Authorization architecture" diagram above for what ClaimBillEntry actually consumes from it. The client-only enforcement of `CBEDiscardBill` is defined once, as [BR-SCR-006](business-rules.md#br-scr-006-discard-permission-is-enforced-client-side-only) in [Business rules](business-rules.md), and as [TD-003](tech-debt.md#td-003-authorization-enforced-on-the-client-or-missing) in [Tech debt](tech-debt.md).

## Data-level authorization

| Entity type | Validation pattern | Enforcement library | Source |
| --- | --- | --- | --- |
| Claim (conflict, proven, package-computed) | `ClaimSecurityService` computes `HasConflictAccess` from the claim's `IsConflictClaim` flag and the caller's `ConflictClaim` resource membership† ([SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas)); consumed only by `BaseController.CheckSecurity` | `SAIF.Services` | `ClaimControllerBase` ctor → `IClaimSecurityService`; `BaseController.CheckSecurity` → `access.HasConflictAccess` |
| Claim (conflict, local, independent) | `EntryController.GetBill` separately validates `ConflictClaim` access via its own local `UserHasAccessToClaim` method — this is not a consumer of `ClaimSecurityService.HasConflictAccess` | `ClaimBillEntry` (`EntryController`) | `EntryController.GetBill` → `IClaimInformationService.GetClaimSecurity().IsConflictClaim` + `ApplicationUser.IsAuthorized("ConflictClaim")` |
| Claim (secure, computed but unconsumed) | `ClaimSecurityService` also computes `HasSecureAccess` from `IsSecureClaim`/`SecureClaim`† ([SVC-BR-SEC-01](business-rules.md#svc-br-sec-01-conflict-and-secure-claim-access-formulas)), but no ClaimBillEntry code path was found consuming it — unknown whether it is enforced elsewhere** | `SAIF.Services` | `ClaimControllerBase` ctor → `IClaimSecurityService` |

## Security dependencies

| Dependency | Type | Purpose |
| --- | --- | --- |
| IIS Windows authentication (NTLM/Kerberos) | Infrastructure | Establishes the `WindowsIdentity` on every request; anonymous access disabled |
| `SAIF.Framework.Web.Mvc` | NuGet package | Ships `SaifAuthorizeAttribute`, the attribute-based `CBPEnterBill` role gate |
| `SAIF.SAIFCode.Core` package (`SAIF.Services` assembly) | NuGet package | Ships `ClaimSecurityService`, the programmatic `ConflictClaim`/`SecureClaim` checks |
| `SAIF.SAIFCode.Web.Mvc` | NuGet package | Ships `ClaimControllerBase`, the base controller that constructs `ClaimSecurityService` |
| Active Directory | Infrastructure | User/role membership store underlying `IsAuthorized`/`IUserProfileService` resolution |

## Notes
- The AD resource catalog intentionally lists 4 entries, not the template's minimum of 5 — see "Scope exception" above.
- `SaifAuthorizeAttribute` (`SAIF.Framework.Web.Mvc`) role-check behavior is documented via retrieved behavior† in [Request pipeline and composition](operations/request-pipeline-and-composition.md). `ClaimControllerBase.CheckSecurity` (`SAIF.SAIFCode.Web.Mvc`) internals beyond its call site remain untraced — not verified by this run.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Data model](data-model.md)
- [Tech debt](tech-debt.md)
- [Hosted endpoints](hosted-endpoints.md)
- [Screen catalog](../screens/index.md)

> **Note:** Statements marked with `†` are derived from dependency library
> documentation via the Legends CLI. Statements marked with `**` could not be
> confirmed; treat as unknown until verified.

## Sources
- ClaimBillEntry/Controllers/EntryController.cs
- ClaimBillEntry/Controllers/BaseController.cs
